package com.yvan.platform;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer;
import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import lombok.SneakyThrows;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class XmlUtils {

    public static final XmlMapper objectXmlMapper;

    static {
        objectXmlMapper = new XmlMapper();
        JacksonXmlModule module = new JacksonXmlModule();
        module.setDefaultUseWrapper(false);

        objectXmlMapper.registerModule(module);
        objectXmlMapper.registerModule(new JodaModule());
        objectXmlMapper.registerModule(JsonWapperSerializer.MODEL);
        objectXmlMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        objectXmlMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        objectXmlMapper.writerWithDefaultPrettyPrinter();
    }

    @SneakyThrows
    public static JsonNode readXmlToNode(File... files) {
        JsonNode root = null;
        for (File file : files) {
            JsonNode jsonNode = readXmlToNode(file);
            if (root == null) {
                root = jsonNode;
            } else {
                YvanUtil.merge(root, jsonNode);
            }
        }

        return root;
    }

    @SneakyThrows
    public static JsonNode readXmlToNode(File file) {
        return readXmlToNode(YvanUtil.readFile(file));
    }

    @SneakyThrows
    public static JsonNode readXmlToNode(String content) {
        Object map = objectXmlMapper.readValue(content, GenericObject.class);
        String json = YvanUtil.toJson(map);
        return YvanUtil.objectMapper.readTree(json);
    }

    @JsonDeserialize(using = CustomDeserializer.class)
    private static class GenericObject {
    }

    private static class CustomDeserializer extends UntypedObjectDeserializer {
        private static final long serialVersionUID = -4628994110702279382L;

        protected Object mapObject(JsonParser jp, DeserializationContext ctxt) throws IOException {
            JsonToken t = jp.getCurrentToken();
            if (t == JsonToken.START_OBJECT) {
                t = jp.nextToken();
            }
            // minor optimization; let's handle 1 and 2 entry cases separately
            if (t == JsonToken.END_OBJECT) {
                // and empty one too
                // empty map might work; but caller may want to modify... so better just give small modifiable
                return new LinkedHashMap<String, Object>(2);
            }
            String field1 = jp.getCurrentName();
            jp.nextToken();
            Object value1 = deserialize(jp, ctxt);
            if (jp.nextToken() == JsonToken.END_OBJECT) { // single entry; but we want modifiable
                LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(2);
                value1 = handleMaultipleValue(result, field1, value1);
                result.put(field1, value1);
                return result;
            }
            String field2 = jp.getCurrentName();
            jp.nextToken();
            Object value2 = deserialize(jp, ctxt);
            if (jp.nextToken() == JsonToken.END_OBJECT) {
                LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>(4);
                value1 = handleMaultipleValue(result, field1, value1);
                result.put(field1, value1);
                value2 = handleMaultipleValue(result, field2, value2);
                result.put(field2, value2);
                return result;
            }
            // And then the general case; default map size is 16
            LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>();
            value1 = handleMaultipleValue(result, field1, value1);
            result.put(field1, value1);
            value2 = handleMaultipleValue(result, field2, value2);
            result.put(field2, value2);
            do {
                String fieldName = jp.getCurrentName();
                jp.nextToken();
                Object value = deserialize(jp, ctxt);
                value = handleMaultipleValue(result, fieldName, value);
                result.put(fieldName, value);
            } while (jp.nextToken() != JsonToken.END_OBJECT);
            return result;
        }

        @SuppressWarnings("unchecked" )
        private Object handleMaultipleValue(Map<String, Object> map,
                                            String key,
                                            Object value) {
            if (!map.containsKey(key)) {
                return value;
            }

            Object originalValue = map.get(key);
            if (originalValue instanceof List) {
                ((List) originalValue).add(value);
                return originalValue;
            } else {
                ArrayList newValue = new ArrayList();
                newValue.add(originalValue);
                newValue.add(value);
                return newValue;
            }
        }

    }


}
